package com.nfit.shopweb.service.impl;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.nfit.shopcommon.exception.CustomException;
import com.nfit.shopmodel.dto.system.AssginRoleDto;
import com.nfit.shopmodel.dto.system.LoginDto;
import com.nfit.shopmodel.dto.system.SysPasswordDto;
import com.nfit.shopmodel.dto.system.SysUserDto;
import com.nfit.shopmodel.entity.system.SysUser;
import com.nfit.shopmodel.vo.common.AuthConst;
import com.nfit.shopmodel.vo.common.ResultCodeEnum;
import com.nfit.shopmodel.vo.system.LoginVo;
import com.nfit.shopweb.mapper.SysUserMapper;
import com.nfit.shopweb.mapper.SysUserRoleMapper;
import com.nfit.shopweb.service.SysRoleService;
import com.nfit.shopweb.service.SysUserService;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;

import javax.swing.undo.CannotUndoException;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import static com.nfit.shopmodel.vo.common.AuthConst.LOGIN_TTL;

/**
 * ClassName:SysUserServiceImpl
 * Package:com.nfit.shopweb.service.impl
 * Description
 *
 * @Author L
 * @Create 2024/11/15 19:32
 */
@Slf4j
@Schema(name = "用户",description = "用户业务实现")
@Service
public class SysUserServiceImpl implements SysUserService {


    @Autowired
    private SysUserMapper sysUserMapper;

    @Autowired
    private SysUserRoleMapper sysUserRoleMapper;

    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    /**
     *实现思路：
     * 第一步：先判断验证码,从LoginDto中可以获取到前端传来的验证码的key和value。
     * 第二步：根据前端传来的key,从redis中获取存入redis中的验证码value,进行比较是否一致,
     *  一致删除redis中的验证码(没登录一次需要新的验证码,所以没有必要一直存储)。
     * 第三步：判断用户名是否存在,如果不存在抛异常,一致进行密码的校验,
     *  根据前端传来的密码进行加密比较(这里与数据库的密码进行比较,注意忽略大小比较),密码校验不一致抛异常
     * 第四步：密码一致,存储当前登入用户到redis中,生成uuid作为存储的key,
     *  也使用LoginVo封装uuid作为token响应给前端，refresh_token 可以为null
     * @param loginDto 封装前端传来的数据
     */
    @Override
    public LoginVo login(LoginDto loginDto) {
//        //第一步：从dto从获取验证码
//        String captcha = loginDto.getCaptcha();
//        String captchaKey = loginDto.getCodeKey();
//        //第二步： 根据key获取存入redis中的验证码
//        String redisCode = redisTemplate.opsForValue().get(AuthConst.VALIDATE_CODE + captchaKey);
//        //判断验证码,忽略大小写判断
//        if (StrUtil.isEmpty(redisCode) || !StrUtil.equalsIgnoreCase(redisCode, captcha)){
//            throw new CustomException(ResultCodeEnum.VALIDATECODE_ERROR);
//        }
//        //验证码通过,删除redis中的验证码
//        redisTemplate.delete(AuthConst.VALIDATE_CODE + captchaKey);


        //第三步：1 判断用户名是否存在
        //从dto中获取登入的用户
        String loginUserName = loginDto.getUserName();
        //调用方法
        SysUser loginUser = sysUserMapper.findByUsername(loginUserName);
        if (loginUser == null) {
            //抛自定义异常
            throw new CustomException(ResultCodeEnum.LOGIN_ERROR);
        }
        //2 判断完用户名,判断密码
        //获取登入密码
        String password = loginDto.getPassword();
        //进行加密与数据库加密的密码比较
        String md5Password = DigestUtils.md5DigestAsHex(password.getBytes());
        if (!md5Password.equals(loginUser.getPassword())) {
            throw new CustomException(ResultCodeEnum.LOGIN_ERROR);
        }

        //第四步： 3 登录成功,生成UUID作为token,返回给前端
        String token = UUID.randomUUID().toString().replaceAll("-","");
        //把登录成功的用户信息存入redis中
        redisTemplate.opsForValue().set(AuthConst.LOGIN_USER+token, JSON.toJSONString(loginUser),LOGIN_TTL, TimeUnit.DAYS);
        //封装token返回给前端
        LoginVo loginVo = new LoginVo();
        loginVo.setToken(token);

        return loginVo;
    }

    /**
     * 退出功能,根据token删除redis中存储的用户信息
     */
    @Override
    public void logout(String token) {
        redisTemplate.delete(AuthConst.LOGIN_USER + token);
    }

    /**
     * 分页
     * @param pageNum 当前页
     * @param pageSize 每页记录数
     * @param sysUserDto 条件
     */
    @Override
    public PageInfo<SysUser> page(int pageNum, int pageSize, SysUserDto sysUserDto) {
        PageHelper.startPage(pageNum,pageSize);
        List<SysUser> list = sysUserMapper.page(sysUserDto);
        return new PageInfo<>(list);
    }

    /**
     * 删除用户
     * @param id  用户id
     */
    @Override
    public void delete(Integer id) {
        sysUserMapper.delete(id);
    }

    /**
     * 修改用户
     * @param sysUser 用户实体根据id修改
     */
    @Override
    public void update(SysUser sysUser) {
        sysUserMapper.update(sysUser);
    }

    /**
     * 添加用户
     * @param sysUser 用户
     */
    @Override
    public void save(SysUser sysUser) {
        //获取用户名
        String userName = sysUser.getUserName();
        //调佣接口
        SysUser byUsername = sysUserMapper.findByUsername(userName);
        //判断
        if (byUsername != null) {
            throw new CustomException(ResultCodeEnum.USER_NAME_IS_EXISTS);
        }
        //设置状态
        sysUser.setStatus(0);
        //获取密码
        String password = sysUser.getPassword();
        //加密
        String md5Password = DigestUtils.md5DigestAsHex(password.getBytes());
        //封装数据
        sysUser.setPassword(md5Password);
        sysUserMapper.save(sysUser);
    }

    /**
     * 保存分配给用户的角色
     * 给方法加事务回滚
     *
     * 实现思路：第一步先删除之前的用户所分配的角色id,根据用户id删除(用户和角色是多对多的关系,数据表sys_user_role)
     * AssginRoleDto 中 userId 是用户id
     * AssginRoleDto中 roleIdList集合中获取前端传来的角色Id(可以是一个或多个角色id)
     * 就此完成用户角色分配
     *
     * @param assginRoleDto 接收前端数据
     */
    @Transactional
    @Override
    public void saveAssignRole(AssginRoleDto assginRoleDto) {
        //保存分配角色之前先删除用户之前保存的角色
        sysUserRoleMapper.deleteByUserId(assginRoleDto.getUserId());

        //分配新角色数据
        List<Long> roleIdList = assginRoleDto.getRoleIdList();
        roleIdList.forEach(roleId -> {
            sysUserRoleMapper.saveAssignRole(assginRoleDto.getUserId(),roleId);
        });
    }

    /**
     * 修改密码
     * @param sysPasswordDto 封装前端传来的数据
     */
    @Transactional
    @Override
    public void updatePassword(SysPasswordDto sysPasswordDto) {
        //获取旧密码
        String password = sysPasswordDto.getPassword();
        //获取新密码
        String newPassword = sysPasswordDto.getNewPassword();
        //获取确认密码
        String verifyPassword = sysPasswordDto.getVerifyPassword();
        //获取用户名
        String userName= sysPasswordDto.getUsername();

        //根据用户名查询用户
        SysUser user = sysUserMapper.findByUsername(userName);
        //log.info("根据用户名查询用户数据：{}",user);
        //进行两次输入的密码校验,这里没有md5加密
        //忽略大小写判断
        if (!newPassword.equalsIgnoreCase(verifyPassword) ){
            throw new CustomException(ResultCodeEnum.LOGIN_ERROR);
        }
        //原来的密码与旧密码MD5加密的校验
        String md5Password = DigestUtils.md5DigestAsHex(password.getBytes());
        //log.info("新密码:{}",md5Password);
        if (!user.getPassword().equalsIgnoreCase(md5Password)){
            throw new CustomException(ResultCodeEnum.LOGIN_ERROR);
        }

        //加密新密码
        String md5NewPassword = DigestUtils.md5DigestAsHex(newPassword.getBytes());
        //执行
        sysUserMapper.updatePassword(userName,md5NewPassword);
        //log.info("执行mapper成功{}",sysUserMapper);
        //更新redis中的用户信息
        //获取token
        String token = sysPasswordDto.getToken();
        //获取redis中的user数据
        String redisUser = redisTemplate.opsForValue().get(AuthConst.LOGIN_USER + token);
        //取出user用户进行重新赋值
        SysUser sysUser = JSON.parseObject(redisUser,SysUser.class);
        sysUser.setPassword(md5NewPassword);
        sysUser.setUserName(userName);
        //log.info("更新sysUser用户：{}",sysUser);
        //在存入redis中
        redisTemplate.opsForValue().set(AuthConst.LOGIN_USER + token,JSON.toJSONString(sysUser),7, TimeUnit.DAYS);
    }
}
